Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

http: don't inherit from Object.prototype #6102

Closed
wants to merge 1 commit into from

Conversation

mscdex
Copy link
Contributor

@mscdex mscdex commented Apr 7, 2016

Checklist
  • tests and code linting passes
  • a test and/or benchmark is included
  • the commit message follows commit guidelines
Affected core subsystem(s)
  • http
Description of change

This commit safely allows header names that are named the same as properties that are ordinarily inherited from Object.prototype such as __proto__.

This commit safely allows header names that are named the same as
properties that are ordinarily inherited from Object.prototype such
as __proto__.
@mscdex mscdex added the http Issues or PRs related to the http subsystem. label Apr 7, 2016
@mscdex
Copy link
Contributor Author

mscdex commented Apr 7, 2016

CI: https://ci.nodejs.org/job/node-test-pull-request/2205/

I also took the liberty of putting the prototype-less constructor in internal/util this time for better reusability. We could reuse this constructor in #6055 and #6092 and anywhere else where having this kind of object would be ideal.

@mscdex mscdex added the semver-major PRs that contain breaking changes and should be released in the next major version. label Apr 7, 2016
@mscdex mscdex added this to the 6.0.0 milestone Apr 7, 2016
@mscdex
Copy link
Contributor Author

mscdex commented Apr 7, 2016

One other thing to note: the check introduced by 2a1ef97 was already preventing weird things from happening when setting header names that coincide with Object.prototype property names, but in some cases the header values would be ignored. With this PR, you can now actually see those special headers when they are received.

@mscdex
Copy link
Contributor Author

mscdex commented Apr 7, 2016

@jasnell
Copy link
Member

jasnell commented Apr 7, 2016

This one could be fairly significant given how extensively the headers property is directly accessed. We'll want to make sure to get extensive review on this. @nodejs/http @nodejs/ctc

That said, the change LGTM

@Fishrock123
Copy link
Contributor

Everyone accesses _headers.

@mscdex
Copy link
Contributor Author

mscdex commented Apr 7, 2016

So it looks like there is a problem that is going to be a common theme for any of these instances where we replace {} with a prototype-less object: users of the should module will run into exceptions when trying to do something like req.headers.should.not.have.property('origin') or parsedQuerystring.should.have.property('asdf') because should only extends Object.prototype by default.

I'm not sure what to do about that...

@jasnell
Copy link
Member

jasnell commented Apr 7, 2016

Yeah, that's the kind of breakage I was afraid of. I'm not sure there's
another solution tho unless we simply say certain types of values simply
aren't permitted... Which sucks.
On Apr 7, 2016 11:47 AM, "Brian White" notifications@github.com wrote:

So it looks like there is a problem that is going to be a common theme for
any of these instances where we replace {} with a prototype-less object:
users of the should module will run into exceptions when trying to do
something like req.headers.should.not.have.property('origin') or
parsedQuerystring.should.have.property('asdf') because should only extends
Object.prototype by default.


You are receiving this because you are on a team that was mentioned.
Reply to this email directly or view it on GitHub
#6102 (comment)

@mscdex
Copy link
Contributor Author

mscdex commented Apr 7, 2016

I suppose these libraries could monkey-patch Object.create() or something like:

var create = Object.create;
Object.create = function() {
  var obj = create.apply(null, arguments);
  if (obj.prototype == null)
    obj.prototype = Object.prototype;
  return obj;
};

Yeah it's hacky, but then again they're already mutating Object.prototype. I dunno...

@jasnell
Copy link
Member

jasnell commented Apr 7, 2016

That's pretty ugly. I'm wondering if there's a chance anything can be done
on the v8 side to optimize the performance of setting the prototype after
creation so we don't get the perf regression. /cc @ofrobots
On Apr 7, 2016 11:55 AM, "Brian White" notifications@github.com wrote:

I suppose these libraries could monkey-patch Object.create() or something
like:

var create = Object.create;Object.create = function() {
var obj = create.apply(null, arguments);
if (obj.prototype == null)
obj.prototype = Object.prototype;
return obj;
};

Yeah it's hacky, but then again they're already mutating Object.prototype.


You are receiving this because you are on a team that was mentioned.
Reply to this email directly or view it on GitHub
#6102 (comment)

@Fishrock123
Copy link
Contributor

Idea: we expose a base null object and let people set onto that for these, then we inherit from it.

@mscdex
Copy link
Contributor Author

mscdex commented Apr 7, 2016

@Fishrock123 So in this case you're saying expose StorageObject and modules like should would call Object.setPrototypeOf(StorageObject, Object.prototype) during startup?

@Fishrock123
Copy link
Contributor

I would actually expose a "BaseObject" directly (Which would be a Object.create(null)) in some way, and then set that as the prototype for these.

I'm not 100% that is a good idea, but it is what first comes to mind..

@mscdex
Copy link
Contributor Author

mscdex commented Apr 7, 2016

@Fishrock123 Wouldn't exposing the prototype vs the constructor complicate things by having to lazily set the constructor's prototype?

@Fishrock123
Copy link
Contributor

@mscdex Wouldn't it still have the same reference everwhere? I don't think there would actually need to be lazy loading or special hooks.

@mscdex
Copy link
Contributor Author

mscdex commented Apr 7, 2016

@Fishrock123 Oh I thought you meant have the ability to replace the prototype entirely and not just mutate it.

@jasnell
Copy link
Member

jasnell commented Apr 8, 2016

working through the options I think it's likely going to be ok to simply require that if someone wants to treat these return values as objects they'll need to set the prototype themselves. We could make it slightly easier by providing a utility function but doing so isn't critical. It is a semver-major change, after all.

const headers = Object.setPrototypeOf(req.headers, Object.prototype);

or, using a utility method:

const headers = util.asObject(req.headers);

@evanlucas
Copy link
Contributor

If this breaks headers.hasOwnProperty() I'm -1 on it.

@addaleax
Copy link
Member

addaleax commented Apr 8, 2016

@evanlucas Thing is, that might also get broken by receiving a __proto__: null header in the HTTP response…

@evanlucas
Copy link
Contributor

@addaleax That is a good point

@evanlucas
Copy link
Contributor

@addaleax but that will be null as a string, not the null value.

@addaleax
Copy link
Member

addaleax commented Apr 8, 2016

@evanlucas Hm yeah, you’re right… this still feels like the right kind of thing to do. At least for HTTP, where incoming headers are always converted to lower-case, maybe one could cherry-pick hasOwnProperty and possibly toString from Object.prototype and leave it at that?

@jasnell
Copy link
Member

jasnell commented Apr 20, 2016

Hmm... that's certainly possible. I would just be a bit worried about user expectations... that is, if the thing has hasOwnProperty and toString from Object, then the user may expect it to be a full regular Object. We can add documentation warning them away from that but it's worth considering. What do you think @mscdex ?

@mscdex
Copy link
Contributor Author

mscdex commented Apr 20, 2016

I think I tend to agree that cherry picking methods like that could be misleading, so it should be all or nothing IMHO.

@mscdex
Copy link
Contributor Author

mscdex commented Apr 20, 2016

Do any other @nodejs/collaborators have any suggestions/opinions/comments/etc. about this? Does @jasnell's suggestion about simply requiring end users to call setPrototypeOf() on these particular objects seem ok? I have no problem with that.

/cc @btd too as he seems to be the maintainer of should and may have some input?

@jasnell
Copy link
Member

jasnell commented Apr 20, 2016

We should get some feedback from folks like @dougwilson also

@MylesBorins
Copy link
Contributor

@mscdex is this something we still want to explore?

@jasnell
Copy link
Member

jasnell commented Dec 29, 2016

This is still something that ultimately should happen but there's still quite a bit of uncertainty around what would actually break. If we're going to do it, then I'm thinking we should be printing deprecation warnings whenever any Object.prototype methods or properties are accessed on this object in Node.js 8.0.0 and actually do switch in Node.js 9.0.0

@mscdex
Copy link
Contributor Author

mscdex commented Dec 29, 2016

@thealphanerd I've always been for it, hence the PR, but the issue for http has always been breakage. Apparently more people are affected by that than the same changes we made to querystring.parse().

@jasnell jasnell added the stalled Issues and PRs that are stalled. label Mar 24, 2017
@fhinkel
Copy link
Member

fhinkel commented Mar 26, 2017

ping @mscdex

@mscdex
Copy link
Contributor Author

mscdex commented Mar 26, 2017

@fhinkel This will be stalled until either the breakage is no longer a concern or the community is weaned off of the likes of res.headers.hasOwnProperty(), etc. in their code.

@richardlau richardlau closed this Mar 26, 2017
@richardlau richardlau reopened this Mar 26, 2017
@richardlau
Copy link
Member

Sorry, accidental close.

@ljharb
Copy link
Member

ljharb commented Mar 26, 2017

@mscdex given that the breakage should always be a concern, and 15+ years of it being a bad practice to chain .hasOwnProperty off of objects hasn't weaned people off of it sufficiently, that suggests this will never make progress unless it's decided to go ahead and suffer through the breakage in v8 or v9 (eg).

@mscdex
Copy link
Contributor Author

mscdex commented Mar 26, 2017

@ljharb That was just one example though. As mentioned earlier in this PR, another example is users adding to Object.prototype, which was actually being done by some popular modules on npm (e.g. should). So with the changes in this PR, some tests would break, such as res.headers.should.have.property(...), and CTC would also need to agree to allowing breakage of those such cases as well.

@ljharb
Copy link
Member

ljharb commented Mar 26, 2017

Gotcha. I consider "should" patterns deprecated in favor of "expect" patterns, but just like normal chaining that's still going to be around forever. Has the CTC been able to discuss this yet and come to a conclusion? If not, could it go on the agenda?

Copy link
Contributor

@refack refack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add note about type in docs

@refack
Copy link
Contributor

refack commented May 16, 2017

If this ever matures is should include notes in the docs similier to e1cabf6#diff-69e1ac0b5bfc06e74f2c1ab7b062c6af

@Trott
Copy link
Member

Trott commented Aug 16, 2017

@mscdex Should this remain open? Does it make sense to at least rebase so we can CITGM it again?

@jasnell
Copy link
Member

jasnell commented Aug 24, 2017

Going to close this due to lack of forward progress. @mscdex ... when you're ready to revisit this, feel free to reopen or open a new PR

@jasnell jasnell closed this Aug 24, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
http Issues or PRs related to the http subsystem. semver-major PRs that contain breaking changes and should be released in the next major version. stalled Issues and PRs that are stalled.
Projects
None yet
Development

Successfully merging this pull request may close these issues.